package projet.database;

import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import org.json.JSONObject;

import projet.CustomException;
import projet.Tools;
import projet.User;
import projet.dataaccess.DataAccess;
import projet.dataaccess.PathErrorException;
import projet.dataaccess.XML;
import projet.item.Item;
import projet.metadata.Commentaire;
import projet.metadata.MetaData;
import projet.metadata.Tag;

/**
 * The database class provides a facility to store CD and video objects. A list
 * of all CDs and videos can be printed to the terminal. This version does not
 * save the data to disk, and it does not provide any search functions.
 * 
 * @author CORSINI Pierre, Cyril CHANDELIER, Sylvain BRUN
 */
public class Database {

	private final int BASEID = 0;
	private HashMap<Integer, ArrayList<String>> dictionnaries;
	private String itemfile;
	private HashMap<Integer, Item> items;
	private HashMap<Integer, MetaData> metadatas;
	private String metafile;
	private String path;
	private final String pathSeparator = System.getProperty("file.separator");
	private String userdir;
	private HashMap<Integer, User> users;

	/**
	 * Construct a database from the given path
	 * 
	 * @throws CustomException
	 */
	public Database(String path) throws CustomException {
		this.items = new HashMap<Integer, Item>();
		this.users = new HashMap<Integer, User>();
		this.metadatas = new HashMap<Integer, MetaData>();
		this.dictionnaries = new HashMap<Integer, ArrayList<String>>();
		this.path = path;
		this.itemfile = path + this.pathSeparator + "items.xml";
		this.metafile = path + this.pathSeparator + "metadatas.xml";
		this.userdir = path + this.pathSeparator + "user";
		if (path == null || path == "")
			throw new CustomException("Path must be valid !");

		Tools.print(this.load());
	}

	/**
	 * Add an item to the database, telling who own it
	 * 
	 * @param userId the owner
	 * @param theItem the item
	 * @throws CustomException
	 */
	private void addItem(Integer userId, Item theItem) throws CustomException {
		this.userAddItem(this.getUser(userId), theItem);
	}

	/**
	 * Add a meta to an item
	 * 
	 * @param itemID the item id
	 * @param meta the meta to add
	 * @throws CustomException
	 */
	private void addMetaData(int itemID, MetaData meta) throws CustomException {
		Item item = this.items.get(itemID);
		if (item == null) {
			throw new CustomException("This item doesn't exist");
		}
		this.metadatas.put(meta.getId(), meta);
		this.items.get(itemID).addMetaData(meta);
	}

	/**
	 * Add a tag to an item and stop it in the db
	 * 
	 * @param itemID the item that'll be linked to the tag
	 * @param tag the tag to add
	 * @throws CustomException
	 */
	private void addTag(int itemID, Tag tag) throws CustomException {
		try {
			Tag t = this.getTag(tag.getName());
			this.addMetaData(itemID, t);
		} catch (CustomException e) {
			this.addMetaData(itemID, tag);
		}
	}

	/**
	 * Add an user to the database.
	 * 
	 * @param theuser The user to be added.
	 * @throws CustomException
	 */
	private void addUser(User theUser) throws CustomException {
		this.users.put(theUser.getId(), theUser);
		this.saveUser(theUser);
	}

	/**
	 * 'can the user edit the item ?'
	 * 
	 * @param u the user
	 * @param i the item
	 * @return true if he can
	 */
	private boolean canUserEditItem(User u, Item i) {
		return i.getOwnerId() == u.getId();
	}

	/**
	 * 'can the user see the item ?'
	 * 
	 * @param u the user
	 * @param i the item
	 * @return true if he can
	 * @throws CustomException
	 */
	private boolean canUserSeeItem(User u, Item i) throws CustomException {
		User itemOwner = this.getUser(i.getOwnerId());
		return itemOwner == u || itemOwner.isFriendWith(u);
	}

	/**
	 * Test if the username exists
	 * 
	 * @param username The username
	 * @return True if exists
	 */
	private boolean doUsernameExists(String username) {
		try {
			this.getUser(username);
		} catch (CustomException e) {
			return false;
		}

		return true;
	}

	private boolean doUserOwnItem(User theUser, Item theItem) {
		if (theItem.getOwnerId() != theUser.getId()
				|| !theUser.getItems().contains(theItem))
			return false;
		else
			return true;
	}

	/**
	 * Find the biggest key in a Integer Set
	 * 
	 * @param set The set
	 * @return The biggest key
	 */
	private int getBiggestKey(Set<Integer> set) {
		if (set.size() == 0)
			return this.BASEID;

		int max = this.BASEID;
		for (int key : set)
			if (key > max)
				max = key;

		return max;
	}

	/**
	 * Get the next free dictionnary key
	 * 
	 * @return the key
	 */
	public int getFreeDicoId() {
		return this.getBiggestKey(this.dictionnaries.keySet()) + 1;
	}

	/**
	 * Get the next free ID for an Item
	 * 
	 * @return The id
	 */
	public int getFreeItemId() {
		return this.getBiggestKey(this.items.keySet()) + 1;
	}

	/**
	 * Get the next free metadata key
	 * 
	 * @return the key
	 */
	public int getFreeMetaId() {
		return this.getBiggestKey(this.metadatas.keySet()) + 1;
	}

	/**
	 * Get the next free ID for an User
	 * 
	 * @return The id
	 */
	public int getFreeUserId() {
		return this.getBiggestKey(this.users.keySet()) + 1;
	}

	/**
	 * Find an item in the database
	 * 
	 * @param id The id of the item your searching for
	 * @return The item your searching for
	 * @throws CustomException Happens if the id does not exists
	 */
	private Item getItem(int id) throws CustomException {
		if (!this.items.containsKey(id))
			throw new CustomException("Item with id " + id + " not found !");

		return this.items.get(id);
	}

	/**
	 * Get the comments associated to an item
	 * 
	 * @param itemID the item
	 * @return An array list of comments
	 */
	private ArrayList<Commentaire> getItemComments(int itemID) {
		ArrayList<Commentaire> returnedComments = new ArrayList<Commentaire>();
		for (MetaData m : this.metadatas.values()) {
			if (m.getClass() == Commentaire.class
					&& this.items.get(itemID).getMetaDataIDs().contains(
							m.getId())) {
				returnedComments.add((Commentaire) m);
			}
		}
		return returnedComments;
	}

	/**
	 * getItemsByTag() method Returns a set of items which matche with tagID
	 * 
	 * @param tagID Tag's ID
	 * @return Set of items
	 * @throws CustomException
	 */
	private ArrayList<Item> getItemsByTag(Tag tag) throws CustomException {
		ArrayList<String> tagList = this.isInDictionnary(tag.getName());
		ArrayList<Item> returnedItems = new ArrayList<Item>();
		for (String s : tagList) {
			Tag t = this.getTag(s);
			for (Item i : this.items.values()) {
				if (i.getMetaDataIDs().contains(t.getId())) {
					returnedItems.add(i);
				}
			}
		}
		return returnedItems;
	}

	/**
	 * Get the item's tags
	 * 
	 * @param itemID the item id
	 * @return The item's tags
	 */
	private ArrayList<Tag> getItemTags(int itemID) {
		ArrayList<Tag> returnedTags = new ArrayList<Tag>();
		for (MetaData m : this.metadatas.values()) {
			if (m.getClass() == Tag.class
					&& this.items.get(itemID).getMetaDataIDs().contains(
							m.getId())) {
				returnedTags.add((Tag) m);
			}
		}
		return returnedTags;
	}

	/**
	 * Get the metada with the given id
	 * 
	 * @param id the metadata's id
	 * @return the metadata
	 * @throws CustomException
	 */
	private MetaData getMetadata(Integer id) throws CustomException {
		if (this.metadatas.containsKey(id))
			return this.metadatas.get(id);
		else
			throw new CustomException("The metadata with id <" + id
					+ "> do not exists !");
	}

	/**
	 * Get an item's metadatas
	 * 
	 * @param i the item
	 * @return the item's metadata list
	 */
	private ArrayList<MetaData> getMetadata(Item i) {
		ArrayList<MetaData> list = new ArrayList<MetaData>();
		for (Integer id : i.getMetaDataIDs())
			list.add(this.metadatas.get(id));
		return list;
	}

	/**
	 * Get tag
	 * 
	 * @param s the text of the tag
	 * @return the tag
	 * @throws CustomException not found ?
	 */
	private Tag getTag(String s) throws CustomException {
		for (MetaData m : this.metadatas.values()) {
			if (m.getClass() == Tag.class) {
				Tag tag = (Tag) m;
				if (tag.getName().equals(s)) {
					return tag;
				}
			}
		}
		throw new CustomException("Le tag " + s + " n'existe pas");
	}

	/**
	 * Get user
	 * 
	 * @param name The id of the user
	 * @return The user found
	 * @throws CustomException If the use do not exists
	 */
	private User getUser(int id) throws CustomException {
		if (this.users.get(id) == null)
			throw new CustomException("The username with id <" + id
					+ "> do not exists !");

		return this.users.get(id);
	}

	/**
	 * Get user
	 * 
	 * @param name The name of the user
	 * @return The user found
	 * @throws CustomException If the use do not exists
	 */
	private User getUser(String name) throws CustomException {
		for (User iu : this.users.values()) {
			if (iu.getUsername().equals(name))
				return iu;
		}

		throw new CustomException("The username <" + name + "> do not exists !");
	}

	/**
	 * Get the friends of a given user
	 * 
	 * @param u The user
	 * @return A collection of users
	 */
	private ArrayList<User> getUserFriends(User u) {
		ArrayList<User> list = new ArrayList<User>();
		User friend;
		for (Integer id : u.getFriendIDs()) {
			try {
				friend = this.getUser(id);
				if (friend.isFriendWith(u))
					list.add(friend);
			} catch (CustomException e) {

			}
		}

		return list;
	}

	/**
	 * Get the user items
	 * 
	 * @param userID the user id
	 * @return and array list of items
	 */
	private ArrayList<Item> getUserItems(int userID) {
		ArrayList<Item> returnedItems = new ArrayList<Item>();
		for (Item i : this.items.values()) {
			if (i.getOwnerId() == userID)
				returnedItems.add(i);
		}
		return returnedItems;
	}

	/**
	 * Test if the tag is in a dictionnary
	 * 
	 * @param tag the tag
	 * @return true if he is
	 * @throws CustomException
	 */
	private ArrayList<String> isInDictionnary(String tag)
			throws CustomException {
		ArrayList<String> returnedList = new ArrayList<String>();
		returnedList.add(tag);
		for (ArrayList<String> tagList : this.dictionnaries.values()) {
			if (tagList.contains(tag)) {
				System.out.println(tagList);
				return tagList;
			}
		}
		return returnedList;
	}

	/**
	 * Load the database
	 * 
	 * @return True if it worked
	 * @throws CustomException
	 */
	public String load() throws CustomException {
		String log = "Loading database...\n";

		File mainf = new File(this.path);
		if (!mainf.exists())
			try {
				mainf.mkdir();
				log += "Database directories created !\n";
			} catch (Exception e) {
				throw new CustomException(
						"Unable to load database, wrong path !");
			}

		log += this.loadUsers();
		log += this.loadItems();
		log += this.loadMetadatas();
		log += "Database loaded !\n";
		return log;
	}

	/**
	 * loadItems() method Load item's HashMap
	 * 
	 * @throws CustomException
	 */
	private String loadItems() throws CustomException {
		File f = new File(this.itemfile);
		if (!f.exists())
			return "Item file do not exists !\n";

		String log = "";
		int nbError = 0;
		int nbItems = 0;
		XML database = new XML(f);

		String classname;
		Class c = null;
		Constructor co = null;
		Object o = null;
		for (XML container : database.getElements()) {
			// Recupere le nom de la classe correspondante aux donnees
			classname = "projet.item.";
			classname += container.getName();
			classname = classname.substring(0, classname.length() - 1);
			// Vrifie l'existance de la classe

			try {
				c = Class.forName(classname);
				co = c.getConstructor(DataAccess.class);
			} catch (Exception e) {
				log += "La classe d'objet <" + classname + "> est introuvable!";
				continue;
			}

			// Parcours les objets, et essaye de les init
			for (XML xmlitem : container.getElements()) {
				try {
					o = co.newInstance(xmlitem);
					Item casteditem = (Item) o;
					this.addItem(casteditem.getOwnerId(), casteditem);
					nbItems++;
				} catch (Exception e) {
					nbError++;
				}
			}
		}
		return "Item's database loading finished, " + nbItems
				+ " items loaded with " + nbError + " errors !\n";

	}

	/**
	 * loadMetadatas() method Load metadata's HashMap
	 * 
	 * @throws CustomException
	 */

	private String loadMetadatas() throws CustomException {
		this.metadatas = new HashMap<Integer, MetaData>();
		this.dictionnaries = new HashMap<Integer, ArrayList<String>>();
		File f = new File(this.metafile);
		if (!f.exists())
			return "Metadatas file do not exists !\n";

		int nbError = 0;
		int nbMeta = 0;
		XML da = new XML(f);
		if (da.doPathExists("tags"))
			for (XML t : da.getElements("tags")) {
				Tag tag;
				try {
					tag = new Tag(t);
					this.metadatas.put(tag.getId(), tag);
					if (tag.getDico() != -1) {
						if (this.dictionnaries.containsKey(tag.getDico()))
							this.dictionnaries.get(tag.getDico()).add(
									tag.getName());
						else {
							ArrayList<String> tagList = new ArrayList<String>();
							tagList.add(tag.getName());
							this.dictionnaries.put(tag.getDico(), tagList);
						}
					}
					nbMeta++;
				} catch (PathErrorException e) {
					nbError++;
				}
			}
		
		if (da.doPathExists("commentaires"))
			for (XML c : da.getElements("commentaires")) {
				Commentaire commentaire;
				try {
					commentaire = new Commentaire(c);
					this.metadatas.put(commentaire.getId(), commentaire);
					nbMeta++;
				} catch (PathErrorException e) {
					nbError++;
				}

			}
		return "Metadata's database loading finished, " + nbMeta
				+ " loaded with " + nbError + " errors !\n";
	}

	/**
	 * Load users
	 * 
	 * @return log
	 * @throws CustomException
	 */
	private String loadUsers() throws CustomException {
		String log = "";
		int nbError = 0;
		int nbUsers = 0;
		File userf = new File(this.userdir);

		if (!userf.exists() || !userf.isDirectory()) {
			userf.mkdir();
			log += "User directorie created !\n";
		}

		// Feeds the database
		// a) Add users
		User tmpuser;
		for (File ifile : userf.listFiles())
			try {
				tmpuser = new User(new JSONObject(Tools.readFile(ifile)));
				this.users.put(tmpuser.getId(), tmpuser);
				nbUsers++;
			} catch (Exception e) {

				log += "Database loading user error : unable to load "
						+ ifile.getName() + "\n";
				nbError++;
			}

		log += "User's database loading finished, " + nbUsers + " loaded with "
				+ nbError + " errors !\n";

		return log;

	}

	/**
	 * Reload the db from scratch
	 * 
	 * @throws CustomException
	 */
	public void reload() throws CustomException {
		new Database(this.path);
	}

	/**
	 * Remove an item to the database.
	 * 
	 * @param theItem The item to be removed.
	 */
	private boolean removeItem(Item theItem) {
		return null != this.items.remove(theItem.getId());
	}

	/**
	 * Remove a metadata from the db
	 * 
	 * @param m the metadata
	 * @return
	 */
	private boolean removeMetaData(MetaData m) {
		return null != this.metadatas.remove(m.getId());
	}

	/**
	 * Save the db
	 * 
	 * @throws CustomException
	 */
	public void save() throws CustomException {
		this.saveUsers();
		this.saveItems();
		this.saveMetadatas();
	}

	/**
	 * saveItems() method Save items into txt file
	 * 
	 * @throws PathErrorException
	 * @throws CustomException
	 */
	private String saveItems() throws PathErrorException {
		XML da = new XML();
		XML neud;
		for (Item i : this.items.values()) {
			String container = i.getClass().getSimpleName() + "s";
			if (da.doPathExists(container)) {
				neud = da.moveToPath(container, false);
			} else {
				neud = da.moveToPath(container, true);
			}
			neud.addElement(null, i.toMediaAccess());
		}
		Tools.writeFile(new File(this.itemfile), da.getFormatedFileContent());
		return "";
	}

	/**
	 * saveMetadatas() method Save metadatas into the txt file
	 * 
	 * @throws CustomException
	 */
	private String saveMetadatas() throws CustomException {
		XML da = new XML();
		XML neud;
		for (MetaData m : this.metadatas.values()) {
			String container = m.getClass().getSimpleName().toLowerCase() + "s";

			if (da.doPathExists(container)) {
				neud = da.moveToPath(container, false);
			} else {
				neud = da.moveToPath(container, true);
			}
			neud.addElement(null, m.toMediaAccess());
		}
		Tools.writeFile(new File(this.metafile), da.getFormatedFileContent());
		return "";
	}

	/**
	 * Saves the User
	 * 
	 * @param u The user
	 * @return True if it worked
	 * @throws PathErrorException
	 */
	private boolean saveUser(User u) throws PathErrorException {
		String path = this.userdir + this.pathSeparator + "user" + u.getId();
		File tmp = new File(path);
		return Tools.writeFile(tmp, u.toDataAccess().getFormatedFileContent());
	}

	/**
	 * Sauvegarde les utilisateurs
	 * 
	 * @throws CustomException
	 */
	private void saveUsers() throws CustomException {
		for (User u : this.users.values()) {
			try {
				this.saveUser(u);
			} catch (PathErrorException e) {
				throw new CustomException("Unable to save user <id:"
						+ u.getId() + ">");
			}
		}
	}

	/**
	 * Add a friend to the given user
	 * 
	 * @param u The user
	 * @param friendUsername The name of his future friend
	 * @throws CustomException If the future friend do not exists
	 */
	public void userAddFriend(User u, String friendUsername)
			throws CustomException {

		if (friendUsername.equals(u.getUsername()))
			throw new CustomException("You can't add yourself as friend... !");

		User potentialFriend = this.getUser(friendUsername);
		if (potentialFriend == null)
			throw new CustomException("The user " + friendUsername
					+ " do not exists !");

		u.addFriend(potentialFriend);
	}

	/**
	 * Add an item to the database.
	 * 
	 * @param theItem The item to be added.
	 * @param theUser The user that is adding the item.
	 */
	public void userAddItem(User theUser, Item theItem) {
		if (theItem.getId() == Item.UNDEFID)
			theItem.setId(this.getFreeItemId());

		theUser.addItem(theItem);
		theItem.setOwnerId(theUser.getId());
		this.items.put(theItem.getId(), theItem);
	}

	/**
	 * Do this action 'user want to add meta to item'
	 * 
	 * @param u the user
	 * @param itemID the item id
	 * @param meta the meta
	 * @throws CustomException
	 */
	public void userAddMeta(User u, Integer itemID, MetaData meta)
			throws CustomException {
		Item item = this.getItem(itemID);

		// Commentaire
		if (meta.getClass() == Commentaire.class)
			this.addMetaData(item.getId(), meta);
		// Tag
		else if (meta.getClass() == Tag.class && this.canUserEditItem(u, item))
			this.addTag(itemID, (Tag) meta);

		// Default case
		else if (this.canUserEditItem(u, item))
			this.addMetaData(item.getId(), meta);
		else
			throw new CustomException("You cannot add metada on this item !");

	}

	/**
	 * Do this action 'an user want to associate two tags'
	 * 
	 * @param tag1_s tag
	 * @param tag2_s tag
	 * @throws CustomException
	 */
	public void userAssociateTags(String tag1_s, String tag2_s)
			throws CustomException {

		Tag tag1 = this.getTag(tag1_s);
		Tag tag2 = this.getTag(tag2_s);
		if (tag1.getDico() != -1)
			tag2.setDico(tag1.getDico());
		else {
			int dicoID = this.getFreeDicoId();
			tag1.setDico(dicoID);
			tag2.setDico(dicoID);
		}
		this.saveMetadatas();
		this.loadMetadatas();
	}

	/**
	 * Do this action 'an user want to change his pw'
	 * 
	 * @param user the user
	 * @param pwd his new pw
	 */
	public void userChangePassword(User user, String pwd) {
		user.changePsswd(pwd);
	}

	/**
	 * Changes the name of a user
	 * 
	 * @param user The user
	 * @param u The new username
	 * @throws CustomException
	 */
	public void userChangeUsername(User user, String u) throws CustomException {
		if (this.doUsernameExists(u))
			throw new CustomException("Username already used !");

		user.changeUsername(u);
	}

	/**
	 * Do this action 'an user want to create an user(obj)'
	 * 
	 * @param username the username
	 * @param pw his password
	 * @throws CustomException
	 */
	public void userCreateUser(String username, String pw)
			throws CustomException {
		if (this.doUsernameExists(username))
			throw new CustomException("The username is already used !");

		this.addUser(new User(this.getFreeUserId(), username, pw));

	}

	/**
	 * Make a user change the properties of an item
	 * 
	 * @param user
	 * @param item
	 * @throws CustomException
	 */
	public void userEditItem(User user, Integer itemID) throws CustomException {
		Item item = this.getItem(itemID);
		if (!this.doUserOwnItem(user, item))
			throw new CustomException("You do not own this item !");

		item.AskEdit();
	}

	/**
	 * Do this action 'an user want to get his friend's items'
	 * 
	 * @param u the user
	 * @return the text
	 */
	public String userGetFriendItemsList(User u) {

		ArrayList<User> friends = this.getUserFriends(u);
		if (friends.size() == 0)
			return "You have no friends :'( !";

		String ret = "Friend items:\n";

		for (User iu : friends) {
			ArrayList<Item> fitems = this.getUserItems(iu.getId());
			ret += "  " + iu.getUsername() + "(" + fitems.size() + " items)\n";
			for (Item ii : fitems)
				ret += "    " + ii.toString()+"\n";
		}

		return ret.substring(0, ret.length()-1);//Remove last \n
	}

	/**
	 * Do this action 'an user wants to get a list of his friends'
	 * 
	 * @param u the user
	 * @return result
	 */
	public String userGetFriendList(User u) {
		ArrayList<User> friends = this.getUserFriends(u);
		if (friends.size() == 0)
			return "You have no friends :'( !";

		String ret = "Friend: ";

		for (User iu : friends)
			ret += iu.getUsername() + " ";
		return ret;
	}

	/**
	 * Do this action 'an user wants get a list of his items'
	 * 
	 * @param u the user
	 * @return the list
	 */
	public String userGetItemList(User u) {
		ArrayList<Item> userItems = this.getUserItems(u.getId());
		if (userItems.size() == 0)
			return "Vous n'avez pas d'item";

		String ret = "Your items:\n";
		for (Item ii : userItems)
			ret += "  " + ii.toString() + "\n";

		return ret;
	}

	/**
	 * Do this action 'an user want to get a list of items with the same tag'
	 * 
	 * @param u the user
	 * @param tag_s the tag string
	 * @return result
	 * @throws CustomException
	 */
	public String userGetItemListWithTag(User u, String tag_s)
			throws CustomException {
		String ret = "Items with the tag <" + tag_s + ">:\n";
		Tag tag;

		tag = this.getTag(tag_s);
		for (Item i : this.getItemsByTag(tag))
			if (this.canUserSeeItem(u, i))
				ret += "  " + i.toString() + "\n";

		if (ret != "")
			return ret;
		else
			return "No item with this tag !";
	}

	/**
	 * cf function name
	 * 
	 * @param u the user
	 * @param itemID the item id
	 * @return the list of meta
	 * @throws CustomException
	 */
	public ArrayList<MetaData> userGetMeta(User u, Integer itemID)
			throws CustomException {
		return this.getMetadata(this.getItem(itemID));
	}

	/**
	 * Public method to get user
	 * 
	 * @param username the username
	 * @param pw his password
	 * @return the user
	 * @throws CustomException
	 */
	public User userGetUser(String username, String pw) throws CustomException {
		User u = this.getUser(username);
		if (!u.PasswordEquals(pw))
			throw new CustomException("Incorrect password !");
		return u;

	}

	/**
	 * cf function name
	 * 
	 * @return the list of usernames
	 */
	public ArrayList<String> userGetUsernames() {
		ArrayList<String> l = new ArrayList<String>();
		for (User u : this.users.values())
			l.add(u.getUsername());
		return l;
	}

	/**
	 * cf function name
	 * 
	 * @param u the user
	 * @param itemId the item id
	 * @throws CustomException
	 */
	public void userPlayItem(User u, Integer itemId) throws CustomException {
		Item i = this.getItem(itemId);
		if (this.canUserSeeItem(u, i))
			i.play();
		else
			throw new CustomException("You cannot access this item !");
	}

	/**
	 * cf function name
	 * 
	 * @param u the user
	 * @param itemid the item id
	 * @param commentID the comment id
	 * @throws CustomException
	 */
	public void userRemoveComment(User u, Integer itemid, Integer commentID)
			throws CustomException {
		Item i = this.getItem(itemid);
		i.removeMetaData(commentID);
		this.removeMetaData(this.getMetadata(commentID));
	}

	/**
	 * Remove a user's friend
	 * 
	 * @param u The user
	 * @param friendUsername The name of the user to remove
	 * @throws CustomException If the friendUsername do not exists
	 */
	public void userRemoveFriend(User u, String friendUsername)
			throws CustomException {
		User potentialExFriend = this.getUser(friendUsername);

		if (!u.getFriendIDs().contains(potentialExFriend.getId()))
			throw new CustomException("You dont have " + friendUsername
					+ " as friend !");

		u.removeFriend(potentialExFriend);
	}

	/**
	 * Remove an item to the database.
	 * 
	 * @param theItem The item to be removed.
	 * @param theUser The user that is removing the item.
	 * @throws CustomException
	 */
	public void userRemoveItem(User theUser, Integer itemID)
			throws CustomException {
		Item theItem = this.getItem(itemID);
		if (!this.doUserOwnItem(theUser, theItem))
			throw new CustomException("You do not own this item !");

		theUser.removeItem(theItem);
		this.removeItem(theItem);
	}

	/**
	 * cf function name
	 * 
	 * @param u the user
	 * @param itemID the item id
	 * @param tag_s the tag string
	 * @throws CustomException
	 */
	public void userRemoveTag(User u, Integer itemID, String tag_s)
			throws CustomException {
		Item item = this.getItem(itemID);
		Tag tag = this.getTag(tag_s);

		if (item.getMetaDataIDs().contains(tag.getId()))
			item.removeMetaData(tag.getId());
		else
			throw new CustomException("This item is not tagged with this tag !");
	}

	/**
	 * cf function name
	 * 
	 * @param u the user
	 * @param itemID the item id
	 * @throws CustomException
	 */
	public void userShowItem(User u, int itemID) throws CustomException {
		Item item = this.getItem(itemID);
		if (!this.canUserSeeItem(u, item))
			throw new CustomException("You cannot access this item !");

		Tools.print("Informations:");
		item.affichage();
		// Commentaires
		Tools.print("Commentaires:");
		ArrayList<Commentaire> comments = this.getItemComments(itemID);
		if (comments.size() == 0)
			Tools.print("Aucun commentaire");
		else
			for (Commentaire c : comments) {
				Tools.print(2, this.users.get(c.getUserId()).getUsername()
						+ " wrotes : " + c.getText());
			}

		// Tags
		Tools.print("Tags:");
		ArrayList<Tag> tags = this.getItemTags(itemID);
		if (tags.size() == 0)
			Tools.print("This item is not tagged");
		else {
			for (Tag t : tags) {
				Tools.print(2, "- " + t.getName());
			}
		}
	}
}
